package com.jiangruan.javaproject.address;

import com.jiangruan.javaproject.utils.Base58;
import com.jiangruan.javaproject.utils.Hash;

import java.security.*;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECPoint;
import java.util.Arrays;

/**
 * @author:SAVA ME(未蒙救赎)
 * @email:1902586238@qq.com
 * @desc:该类用于推导原始格式的比特币地址
 * @datetime:2021/12/23 10:40
 **/
public class Address {
    /**
     * 该方法用于生成一对随机密钥，并将生成的密钥对返回
     *
     * @return 生成的密钥对对象
     */
    public KeyPair generateKeys() {
        try {
            KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
            //secp256k1,secp256r1
            ECGenParameterSpec spec = new ECGenParameterSpec("secp256k1");
            generator.initialize(spec);
            return generator.generateKeyPair();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 该方法用于根据给定的密钥数据，推导生成对应的原始格式的比特币地址
     *
     * @param keyPair 生成的密钥对
     * @return 推导生成的比特币地址
     */
    public String newAddress(KeyPair keyPair) {
        ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
        ECPoint point = publicKey.getW();
        //原始格式的公钥
        byte[] pubkey = toUncompressedPubKey(point);
        //对公钥进行哈希计算
        byte[] pubkeyHash256 = Hash.SHA256(pubkey);
        byte[] pubHash = Hash.RIPEMD160(pubkeyHash256);

        byte[] version = new byte[1];
        version[0] = 0x00;//版本号

        byte[] ver_pubhash = new byte[version.length + pubHash.length];
        System.arraycopy(version, 0, ver_pubhash, 0, version.length);
        System.arraycopy(pubHash, 0, ver_pubhash, 1, pubHash.length);

        //双哈希，取前4个字节
        byte[] hash1 = Hash.SHA256(ver_pubhash);
        byte[] hash2 = Hash.SHA256(hash1);
        byte[] check = new byte[4];
        System.arraycopy(hash2, 0, check, 0, check.length);

        byte[] ver_pubhash_check = new byte[ver_pubhash.length + check.length];
        System.arraycopy(ver_pubhash, 0, ver_pubhash_check, 0, ver_pubhash.length);
        System.arraycopy(check, 0, ver_pubhash_check, ver_pubhash.length, check.length);

        //随拼接到的字节数组进行base58编码
        String addr = Base58.encode(ver_pubhash_check);
        System.out.println(addr);

//        //公钥数据进行计算
//        //原始格式公钥：04 + x坐标(32字节) + y坐标(32字节)
//        byte[] xBytes = point.getAffineX().toByteArray();
//        byte[] yBytes = point.getAffineY().toByteArray();
//        System.out.println(xBytes.length);//32
//        System.out.println(yBytes.length);//32
//        //x:32   33
//        //32 刚刚好  0[1,32]
//        //33  0[1,32]
//        //y:32   33
//        //32 刚刚好  0[1,32]
//        //33  0[1,32]
        return Base58.encode(ver_pubhash_check);
    }

    /**
     * 校验比特币的地址是否符合规范，并返回校验结果
     *
     * @param addr 要校验的字符串
     * @return 校验结果ture表示符合规范，false表示不符合规范
     */
    public boolean validateAddr(String addr) {
        if ("".equals(addr) || addr == null) {
            return false;
        }
        byte[] ver_pubhash_check = Base58.decode(addr);
        if (ver_pubhash_check.length <= 4) {
            return false;
        }
        byte[] check = new byte[4];
        System.arraycopy(ver_pubhash_check, ver_pubhash_check.length - 4, check, 0, 4);

        byte[] ver_pubhash = new byte[ver_pubhash_check.length - 4];
        System.arraycopy(ver_pubhash_check, 0, ver_pubhash, 0, ver_pubhash.length);

        //霜hash，取前四个字节
        byte[] hash1 = Hash.SHA256(ver_pubhash);
        byte[] hash2 = Hash.SHA256(hash1);
        byte[] check_sum = new byte[4];
        if (hash2 == null) {
            return false;
        }
        System.arraycopy(hash2, 0, check_sum, 0, check_sum.length);

        //比较两个字节数组是否相等
        return Arrays.equals(check, check_sum);
    }

    /**
     * 该方法用于生成非压缩格式的公钥数据，并返回
     *
     * @return 非压缩格式公钥
     */
    public static byte[] toUncompressedPubKey(ECPoint point) {
        byte[] xBytes = point.getAffineX().toByteArray();
        byte[] yBytes = point.getAffineY().toByteArray();

        //原始格式：04 + x坐标 + y坐标 =65字节
        byte[] pubkey = new byte[1 + 2 * 32];
        pubkey[0] = 04;
        if (xBytes.length == 32) {
            //通过拷贝来完成数组拼接
            System.arraycopy(xBytes, 0, pubkey, 1, xBytes.length);
        } else if (xBytes.length > 32) {
            //只读取32字节
            System.arraycopy(xBytes, 1, pubkey, 1, 32);
        }
        //y坐标
        if (yBytes.length == 32) {
            System.arraycopy(yBytes, 0, pubkey, 33, yBytes.length);
        } else if (yBytes.length > 32) {
            System.arraycopy(yBytes, 1, pubkey, 33, 32);
        }
        return pubkey;
    }
}
//    //该main只在测试时使用
//    public static void main(String[] args) {
//        Address addr = new Address();
//        KeyPair keyPair = addr.generateKeys();
//        String address = addr.newAddress(keyPair);
//        boolean isValid = addr.validateAddr(address);
//        System.out.println(isValid);
//    }
//}
